package com.jzt.jk.util;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.text.CharSequenceUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.Feature;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.crypto.dsig.SignatureMethod;

/**
 * openApi 验签工具类
 *
 * @Author xuyl
 * @Date 2022/11/17 10:38
 * @Version 1.5
 */
public class SignatureUtil {


    private static final String ENCODE_TYPE = "UTF-8";
    private static final String ALGORITHM = "HmacSHA1";
    private static final String SEPARATOR = "&";
    private static final String EQUAL = "=";

    private static final String API_VERSION = "2022-12-01";
    public static final String HEADER_SIGNATURE = "signature";

    public static final String META_ACTION = "action";
    public static final String META_VERSION = "version";
    public static final String META_APP_KEY = "appKey";
    public static final String META_TIMESTAMP = "timestamp";
    public static final String META_NONCE = "nonce";

    public static final List<String> META_HEADER_NAME_VALUE_LIST = Arrays.asList(
            META_ACTION,
            META_VERSION,
            META_APP_KEY,
            META_TIMESTAMP,
            META_NONCE);

    // =========== 使用请求参数构造规范化的请求字符串（Canonicalized Query String）=================

    private static String percentEncode(String value) throws UnsupportedEncodingException {
        return URLEncoder.encode(value, ENCODE_TYPE).replace("+", "%20").replace("*", "%2A").replace("%7E", "~");
    }

    private static String buildCanonicalizedQueryString(Map<String, Object> businessDataMap)
            throws UnsupportedEncodingException {
        if (MapUtil.isEmpty(businessDataMap)) {
            return "";
        }
        // 对参数进行排序
        List<String> sortedKeys = new ArrayList<>(businessDataMap.keySet());
        Collections.sort(sortedKeys);
        StringBuilder temp = new StringBuilder();
        for (String key : sortedKeys) {
            // 此处需要对key和value进行编码
            if (Objects.isNull(businessDataMap.get(key))) {
                continue;
            }
            String value = (businessDataMap.get(key) instanceof String) ?
                    (String) businessDataMap.get(key) : JSON.toJSONString(businessDataMap.get(key));
            temp.append(SEPARATOR).append(percentEncode(key)).append(EQUAL).append(percentEncode(value));
        }
        return temp.toString().substring(1);
    }

    // ============================= 计算签名（包括HMAC签名以及Base64编码）===============================

    private static String buildStringToSign(String canonicalizedQueryString, List<String> metaList)
            throws UnsupportedEncodingException {
        // 生成stringToSign字符
        StringBuilder temp = new StringBuilder();
        metaList.forEach(s -> {
            temp.append(s).append(SEPARATOR);
        });
        temp.append(percentEncode("/")).append(SEPARATOR);
        // 此处需要对canonicalizedQueryString进行编码
        temp.append(percentEncode(canonicalizedQueryString));
        return temp.toString();
    }

    private static String buildSignature(String keySecret, String stringToSign)
            throws UnsupportedEncodingException, InvalidKeyException, NoSuchAlgorithmException {
        SecretKey key = new SecretKeySpec((keySecret + SEPARATOR).getBytes(ENCODE_TYPE), SignatureMethod.HMAC_SHA1);
        Mac mac = Mac.getInstance(ALGORITHM);
        mac.init(key);
        byte[] hashBytes = mac.doFinal(stringToSign.getBytes(ENCODE_TYPE));
        byte[] base64Bytes = Base64.getEncoder().encode(hashBytes);
        String base64Utf8String = new String(base64Bytes, StandardCharsets.UTF_8);
        return URLEncoder.encode(base64Utf8String, ENCODE_TYPE);
    }


    /**
     * 通过请求参数Map
     *
     * @param appSecret       应用秘钥，用于加盐加密
     * @param metaList        公共请求参数和请求方法
     * @param businessDataMap 请求参数对象
     * @return 签名字符串
     * @throws Exception 反射异常/编码异常等
     */
    private static String generateSignature(String appSecret, List<String> metaList,
            Map<String, Object> businessDataMap)
            throws Exception {
        System.out.println(String.format("appSecret= %s, metaList= %s, businessDataMap= %s", appSecret,
                JSON.toJSONString(metaList), JSON.toJSONString(businessDataMap)));
        String canonicalizedQueryString = buildCanonicalizedQueryString(businessDataMap);
        String stringToSign = buildStringToSign(canonicalizedQueryString, metaList);
        System.out.println(String.format("stringToSign= %s", stringToSign));
        return buildSignature(appSecret, stringToSign);
    }

    private static List<String> buildMetaList(Map<String, String> metaDataMap) {
        // 准备公共参数，公共参数请求需要放到header中
        List<String> metaList = new ArrayList<>(SignatureUtil.META_HEADER_NAME_VALUE_LIST.size() + 1);
        // 此处顺序需要严格按照常量集合中定义的顺序处理
        SignatureUtil.META_HEADER_NAME_VALUE_LIST.forEach(metaName -> {
            String metaValue = metaDataMap.get(metaName);
            if (CharSequenceUtil.isNotBlank(metaValue)) {
                metaList.add(metaValue);
            }
        });
        // 添加请求方法
        metaList.add("POST");
        return metaList;
    }

    static Map<String, String> buildMetaDataMap(String action, String appKey) {
        // 准备公共参数，公共参数请求时需要放到header中
        Map<String, String> metaDataMap = new HashMap<>();
        // 请求公共参数
        metaDataMap.put(META_ACTION, action);
        metaDataMap.put(META_VERSION, API_VERSION);
        metaDataMap.put(META_APP_KEY, appKey);
        metaDataMap.put(META_TIMESTAMP, String.valueOf(System.currentTimeMillis()));
        // 随机数生成算法可替换为其他，客户端只需要保证随机数唯一即可
        metaDataMap.put(META_NONCE,
                UUID.randomUUID().toString());
        return metaDataMap;
    }

    /**
     * 发起api请求时生成签名
     *
     * @param appSecret   秘钥
     * @param metaDataMap 元数据
     * @param requestObj  请求对象
     * @return 签名
     * @throws Exception
     */
    static String generateSignature(String appSecret,
            Map<String, String> metaDataMap, Object requestObj)
            throws Exception {
        List<String> metaList = buildMetaList(metaDataMap);
        return generateSignature(appSecret, metaList, BeanUtil.beanToMap(requestObj));
    }

    /**
     * 九州通spi回调接口生成签名，将九州通回传的json格式body传到此方法中，生成签名；
     *
     * @param appSecret 应用秘钥
     * @param msgBody   九州通回传的body体对象
     * @return 签名字符串
     */
    private static String spiGenerateSignature(String appSecret, JSONObject msgBody) throws Exception {
        // 取出公共参数，并填充到metaList中
        List<String> metaList = new ArrayList<>(META_HEADER_NAME_VALUE_LIST.size() + 1);
        for (String metaName : META_HEADER_NAME_VALUE_LIST) {
            Object metaValue = msgBody.get(metaName);
            if (metaValue != null) {
                metaList.add(metaValue.toString());
                // 将已取出的公共参数从消息体中移除，只保留业务参数
                msgBody.remove(metaName);
            }
        }
        // 添加请求方法，九州通spi都是POST请求
        metaList.add("POST");
        // 调用上面的生成签名接口
        return generateSignature(appSecret, metaList, msgBody);
    }


    public static boolean spiCheckSign(String appSecret, String jsonStrMsgBody) throws Exception {
        // 避免json格式化重排序
        JSONObject msgBody = JSON.parseObject(jsonStrMsgBody, Feature.OrderedField);
        String reqSignature = String.valueOf(msgBody.remove(HEADER_SIGNATURE));
        String signature = spiGenerateSignature(appSecret, msgBody);
        return signature.equals(reqSignature);
    }
}
